package main

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/xianlubird/mydocker/cgroups"
	"github.com/xianlubird/mydocker/cgroups/subsystems"

	"math/rand"

	log "github.com/Sirupsen/logrus"
	"github.com/xianlubird/mydocker/container"
)

// Run3_1 这个是mydocker run参数会走进来 本函数是关键入口
// 1. 创建一个子进程，这个子进程是一个隔离了cgroups、namespace的进程。也就是一个容器
// 2. 在创建容器的时侯设置namespace
// 3. 在子进程准备运行的时候设置cgroups
// 4. 容器执行execv系统调用，将进程的上下文替换为用户要运行的指令
// 5. 等待子进程(容器)运行完成
// 创建一个子进程 这个子进程实现了隔离命名空间
// chapter 3.1
// func Run3_1(tty bool, command string) {
// 	// 创建一个子进程
// 	parent := container.NewParentProcess(tty, command)
// 	if parent == nil {
// 		log.Errorf("创建容器进程失败！")
// 		return
// 	}
// 	if err := parent.Start(); err != nil {
// 		log.Error(err)
// 	}

// 	// 等待子进程结束
// 	parent.Wait()

// 	os.Exit(-1)
// }

// Run 初始化一个容器
func Run(tty bool, cmdArray []string, res *subsystems.ResourceConfig, volume string, containerName string) {
	containerID := randStringBytes(10)
	if containerName == "" {
		containerName = containerID
	}

	// 本方法创建一个子进程(容器) 通过pipe进行通信 然后等待此进程执行完
	parent, writePipe := container.NewParentProcess(tty, volume, containerName) // 此函数里面做NameSpace隔离
	if parent == nil {
		log.Errorf("创建容器进程失败！")
		return
	}
	if err := parent.Start(); err != nil {
		log.Error(err)
	}

	// 记录运行时容器信息
	containerName, err := recordContainerInfo(parent.Process.Pid, cmdArray, containerName)
	if err != nil {
		fmt.Println("记录容器信息错误：", err)
	}

	// 为子进程设置进程资源限制 （cgroups）先于sendInitCommand执行 先配置cgroup再运行子进程自己的逻辑
	cgroupManager := cgroups.NewCgroupManager("mydocker-cgroup") // 此结构体里做cgroups处理
	cgroupManager.Set(res)
	cgroupManager.Apply(parent.Process.Pid)

	// 给子进程传递参数
	sendInitCommand(cmdArray, writePipe)

	// 等待子进程结束
	if tty {
		fmt.Println("容器进程ID：", parent.Process.Pid)
		parent.Wait()
		// 删除工作空间
		container.DeleteWorkSpace(container.MyDockerPath, container.MyDockerPath+"/mnt", volume)
		// 删除cgroup配置
		cgroupManager.Destroy()
		// 删除容器记录
		// deleteContainerInfo(containerName)
	} else {
		fmt.Println("容器进程ID：", parent.Process.Pid)
		time.Sleep(time.Second * 30)
	}

}

func deleteContainerInfo(containerName string) {
	dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	if err := os.RemoveAll(dirUrl); err != nil {
		log.Errorf("Remove dir %s error %v", dirUrl, err)
	}
}

func recordContainerInfo(containerPID int, commandArray []string, containerName string) (string, error) {
	id := randStringBytes(10)
	createTime := time.Now().Format("2006-01-02 15:04:05")
	command := strings.Join(commandArray, "")
	if containerName == "" { // 如果用户没有设置容器名 就使用随机生成的
		containerName = id
	}
	containerInfo := &container.ContainerInfo{
		Id:          id,
		Pid:         strconv.Itoa(containerPID),
		Command:     command,
		CreatedTime: createTime,
		Status:      container.RUNNING,
		Name:        containerName,
	}
	jsonBytes, err := json.Marshal(containerInfo)
	if err != nil {
		log.Errorf("记录失败")
		return "", err
	}
	jsonStr := string(jsonBytes)
	dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)
	if err := os.MkdirAll(dirUrl, 0666); err != nil {
		log.Errorf("创建容器信息存储目录 %s 失败 %v", dirUrl, err)
		return "", err
	}
	fileName := dirUrl + "/" + container.ConfigName
	file, err := os.Create(fileName)
	defer file.Close()
	if err != nil {
		log.Errorf("Create file %s error %v", fileName, err)
		return "", err
	}
	if _, err := file.WriteString(jsonStr); err != nil {
		log.Errorf("File write string error %v", err)
		return "", err
	}
	fmt.Println("记录容器信息成功:", containerName)
	return containerName, nil
}

func randStringBytes(n int) string {
	letterBytes := "1234567890"
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

func sendInitCommand(cmdArray []string, writePipe *os.File) {
	command := strings.Join(cmdArray, " ")
	fmt.Println("sendInitCommand[command]: ", command)
	writePipe.WriteString(command)
	writePipe.Close()
}
